# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.

from __future__ import absolute_import

import itertools

from sapling.ext import absorb


class simplefctx:
    def __init__(self, content):
        self.content = content.encode("utf-8")

    def data(self):
        assert isinstance(self.content, bytes)
        return self.content


def insertreturns(x):
    # insert "\n"s after each single char
    if isinstance(x, str):
        return "".join(ch + "\n" for ch in x)
    else:
        return list(map(insertreturns, x))


def removereturns(x):
    # the revert of "insertreturns"
    if isinstance(x, bytes):
        x = x.decode("utf-8")
    if isinstance(x, str):
        return x.replace("\n", "")
    else:
        return list(map(removereturns, x))


def assertlistequal(lhs, rhs, decorator=lambda x: x):
    if lhs != rhs and decorator(lhs) != decorator(rhs):
        raise RuntimeError(
            "mismatch:\n actual:   %r\n expected: %r"
            % tuple(map(decorator, [lhs, rhs]))
        )


def testfilefixup(oldcontents, workingcopy, expectedcontents, fixups=None):
    """([str], str, [str], [(rev, a1, a2, b1, b2)]?) -> None

    workingcopy is a string, of which every character denotes a single line.

    oldcontents, expectedcontents are lists of strings, every character of
    every string denots a single line.

    if fixups is not None, it's the expected fixups list and will be checked.
    """
    expectedcontents = insertreturns(expectedcontents)
    oldcontents = insertreturns(oldcontents)
    workingcopy = insertreturns(workingcopy)
    state = absorb.filefixupstate(list(map(simplefctx, oldcontents)), "path")
    state.diffwith(simplefctx(workingcopy))
    if fixups is not None:
        assertlistequal(state.fixups, fixups)
    state.apply()
    assertlistequal(state.finalcontents, expectedcontents, removereturns)


def buildcontents(linesrevs):
    # linesrevs: [(linecontent : str, revs : [int])]
    revs = set(itertools.chain(*[revs for line, revs in linesrevs]))
    return [""] + ["".join([l for l, rs in linesrevs if r in rs]) for r in sorted(revs)]


# input case 0: one single commit
case0 = ["", "11"]

# replace a single chunk
testfilefixup(case0, "", ["", ""])
testfilefixup(case0, "2", ["", "2"])
testfilefixup(case0, "22", ["", "22"])
testfilefixup(case0, "222", ["", "222"])

# input case 1: 3 lines, each commit adds one line
case1 = buildcontents([("1", [1, 2, 3]), ("2", [2, 3]), ("3", [3])])

# 1:1 line mapping
testfilefixup(case1, "123", case1)
testfilefixup(case1, "12c", ["", "1", "12", "12c"])
testfilefixup(case1, "1b3", ["", "1", "1b", "1b3"])
testfilefixup(case1, "1bc", ["", "1", "1b", "1bc"])
testfilefixup(case1, "a23", ["", "a", "a2", "a23"])
testfilefixup(case1, "a2c", ["", "a", "a2", "a2c"])
testfilefixup(case1, "ab3", ["", "a", "ab", "ab3"])
testfilefixup(case1, "abc", ["", "a", "ab", "abc"])

# non 1:1 edits
testfilefixup(case1, "abcd", case1)
testfilefixup(case1, "ab", case1)

# deletion
testfilefixup(case1, "", ["", "", "", ""])
testfilefixup(case1, "1", ["", "1", "1", "1"])
testfilefixup(case1, "2", ["", "", "2", "2"])
testfilefixup(case1, "3", ["", "", "", "3"])
testfilefixup(case1, "13", ["", "1", "1", "13"])

# replaces
testfilefixup(case1, "1bb3", ["", "1", "1bb", "1bb3"])

# (confusing) replaces
testfilefixup(case1, "1bbb", case1)
testfilefixup(case1, "bbbb", case1)
testfilefixup(case1, "bbb3", case1)
testfilefixup(case1, "1b", case1)
testfilefixup(case1, "bb", case1)
testfilefixup(case1, "b3", case1)

# insertions at the beginning and the end
testfilefixup(case1, "123c", ["", "1", "12", "123c"])
testfilefixup(case1, "a123", ["", "a1", "a12", "a123"])

# (confusing) insertions
testfilefixup(case1, "1a23", case1)
testfilefixup(case1, "12b3", case1)

# input case 2: delete in the middle
"1122331133"
case2 = buildcontents([("11", [1, 2]), ("22", [1]), ("33", [1, 2])])

# deletion (optimize code should make it 2 chunks)
testfilefixup(case2, "", ["", "22", ""], fixups=[(4, 0, 2, 0, 0), (4, 2, 4, 0, 0)])

# 1:1 line mapping
testfilefixup(case2, "aaaa", ["", "aa22aa", "aaaa"])

# non 1:1 edits
# note: unlike case0, the chunk is not "continuous" and no edit allowed
testfilefixup(case2, "aaa", case2)

# input case 3: rev 3 reverts rev 2
case3 = buildcontents([("1", [1, 2, 3]), ("2", [2]), ("3", [1, 2, 3])])

# 1:1 line mapping
testfilefixup(case3, "13", case3)
testfilefixup(case3, "1b", ["", "1b", "12b", "1b"])
testfilefixup(case3, "a3", ["", "a3", "a23", "a3"])
testfilefixup(case3, "ab", ["", "ab", "a2b", "ab"])

# non 1:1 edits
testfilefixup(case3, "a", case3)
testfilefixup(case3, "abc", case3)

# deletion
testfilefixup(case3, "", ["", "", "2", ""])

# insertion
testfilefixup(case3, "a13c", ["", "a13c", "a123c", "a13c"])

# input case 4: a slightly complex case
case4 = buildcontents(
    [
        ("1", [1, 2, 3]),
        ("2", [2, 3]),
        ("3", [1, 2]),
        ("4", [1, 3]),
        ("5", [3]),
        ("6", [2, 3]),
        ("7", [2]),
        ("8", [2, 3]),
        ("9", [3]),
    ]
)

testfilefixup(case4, "1245689", case4)
testfilefixup(case4, "1a2456bbb", case4)
testfilefixup(case4, "1abc5689", case4)
testfilefixup(case4, "1ab5689", ["", "134", "1a3678", "1ab5689"])
testfilefixup(case4, "aa2bcd8ee", ["", "aa34", "aa23d78", "aa2bcd8ee"])
testfilefixup(case4, "aa2bcdd8ee", ["", "aa34", "aa23678", "aa24568ee"])
testfilefixup(case4, "aaaaaa", case4)
testfilefixup(case4, "aa258b", ["", "aa34", "aa2378", "aa258b"])
testfilefixup(case4, "25bb", ["", "34", "23678", "25689"])
testfilefixup(case4, "27", ["", "34", "23678", "245689"])
testfilefixup(case4, "28", ["", "34", "2378", "28"])
testfilefixup(case4, "", ["", "34", "37", ""])

# input case 5: replace a small chunk which is near a deleted line
case5 = buildcontents([("12", [1, 2]), ("3", [1]), ("4", [1, 2])])

testfilefixup(case5, "1cd4", ["", "1cd34", "1cd4"])

# input case 6: base "changeset" is immutable
case6 = ["1357", "0125678"]

testfilefixup(case6, "0125678", case6)
testfilefixup(case6, "0a25678", case6)
testfilefixup(case6, "0a256b8", case6)
testfilefixup(case6, "abcdefg", ["1357", "a1c5e7g"])
testfilefixup(case6, "abcdef", case6)
testfilefixup(case6, "", ["1357", "157"])
testfilefixup(case6, "0123456789", ["1357", "0123456789"])

# input case 7: change an empty file
case7 = [""]

testfilefixup(case7, "1", case7)
